import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

public class PublicLab1FileSet implements PublicFileSet {

	private final int numFiles = 4;
	private final int lineLength = 80;
	int numLines;

	private File primIn, auxIn, resultOut, reportOut;

	private Logger LOG;

	public PublicLab1FileSet(String[] fileNames)
			throws IllegalArgumentException {
		this.setErrorLog();// set up an error log that goes to System.err
		if (fileNames.length != this.numFiles) {
			this.LOG.severe("001: Invalid number of arguments; Expected "
					+ this.numFiles + " receieved " + fileNames.length + " ");
			throw new IllegalArgumentException();// throw an exception if the
													// wrong number of args are
													// entered
		}
		this.primIn = new File(fileNames[0]);
		this.auxIn = new File(fileNames[1]);
		this.resultOut = new File(fileNames[2]);
		this.reportOut = new File(fileNames[3]);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see FileSet#checkFiles()
	 */
	@Override
	public boolean checkFiles() {
		boolean result;
		result = this.writeableOutput(this.reportOut);// make sure report is
														// valid first
		if (result) {
			this.setInfoLog();// once we know it is, set up another log to write
								// to it
		}
		result &= this.writeableOutput(this.resultOut);// check other files for
														// validity
		result &= this.readableInput(this.primIn);
		result &= this.readableInput(this.auxIn);
		return result;
	}

	@Override
	public boolean readableInput(File in) {// check that input files exist and
											// are readable
		boolean canRead = false;
		boolean isDirectory = true;
		try {
			canRead = in.canRead();// checks for existence and readability
		} catch (SecurityException e) {// only throws exception if not readable,
										// which is fine
		}
		isDirectory = in.isDirectory();

		if (!canRead) {
			this.LOG.severe("002: " + in.getName()
					+ " does not exist or is not readable.");// if we can't use
																// it log an
																// error
		}
		if (isDirectory) {
			this.LOG.severe("003: " + in.getName() + " is a directory.");// if
																			// we
																			// can't
																			// use
																			// it
																			// log
																			// an
																			// error
		}
		return canRead && !isDirectory;
	}

	@Override
	public boolean writeableOutput(File out) {// check that output files don't
												// exist and are in writeable
												// directories
		File parent = out.getParentFile();// temp variable to get info on parent
											// directory
		if (parent == null) {
			parent = new File(".");
		}
		boolean thisExists = false;
		boolean parentWriteable = false;
		try {
			thisExists = out.exists();
			parentWriteable = new File(".").canWrite();
		} catch (SecurityException e) {// only throws exception if parent isn't
										// writeable, which is fine
		}
		if (thisExists) {
			this.LOG.severe("004: " + out.getName() + " already exists.");// record
																			// error
																			// if
																			// output
																			// file
																			// already
																			// exists
		}
		if (!parentWriteable) {
			this.LOG.severe("005: " + out.getName()
					+ " is in a directory which is not writeable");// record
																	// error if
																	// parent
																	// directory
																	// isn't
																	// writeable
		}
		return (!thisExists) && parentWriteable;
	}

	@Override
	public void setErrorLog() {// Set up error log to System.err
		this.LOG = Logger.getLogger("Lab1.FileSet");
		this.LOG.setUseParentHandlers(false);
		ConsoleHandler errHandler = new ConsoleHandler();
		errHandler.setFormatter(new ErrorFormatter());
		errHandler.setLevel(Level.SEVERE);
		this.LOG.addHandler(errHandler);
	}

	@Override
	public void setInfoLog() {// Set up info log to report file
		try {
			FileHandler infoHandler = new FileHandler(this.reportOut.toString());
			infoHandler.setLevel(Level.INFO);
			this.LOG.addHandler(infoHandler);
		} catch (SecurityException e) {
			// Already checked
		} catch (IOException e) {

		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see FileSet#processFiles()
	 */
	@Override
	public void processFiles() {
		this.numLines = this.processAux();
		if (this.numLines > 0) {
			this.processPrim();
		}
	}

	@Override
	public int processAux() {
		int value = 0;
		FileInputStream auxStream = null;
		try {
			auxStream = new FileInputStream(this.auxIn);
			int next = auxStream.read();
			if (CharInfo.isASCIIDigit(next) && (next != CharInfo.ASCIIZERO)) {
				value += next - CharInfo.ASCIIZERO;
			} else {
				this.LOG.severe("006: Invalid auxillary input file. First character must be an ASCII digit other than 0");
			}
			next = auxStream.read();
			while (CharInfo.isASCIIDigit(next)) {
				value *= 10;
				value += next - CharInfo.ASCIIZERO;
			}
			boolean fail;
			if (next == -1) {
				fail = false;// end of file
			} else if (next == CharInfo.MACNEWLINE) {// new line
				next = auxStream.read();
				fail = (next != -1);// end of file
			} else {
				fail = true;
			}
			if (fail) {
				this.LOG.severe("007: Invalid auxillary input file. File must contain an ASCII number followed by either (only) a new line, or nothing");
				value = 0;
			}
		} catch (FileNotFoundException e) {
			// TODO //Should already be handled? How do we treat this?
		} catch (IOException e) {
			// TODO //Should already be handled? How do we treat this?
		} finally {
			if (auxStream != null) {
				try {
					auxStream.close();
				} catch (IOException e) {

				}
			}
		}
		return value;
	}

	@Override
	public void processPrim() {
		int next = 0;
		FileInputStream primStream = null;
		FileOutputStream resultStream = null;
		try {
			int overflowLines = 0;
			int totalLines = 1;
			int index = 0;
			primStream = new FileInputStream(this.primIn);
			resultStream = new FileOutputStream(this.resultOut);
			next = primStream.read();

			while (next != -1) {
				if (totalLines < this.numLines) {// process and print
					if (next == CharInfo.MACNEWLINE) {
						if ((totalLines + 1) < this.numLines) {
							resultStream.write(next);
						}
						totalLines++;
					} else {
						if (CharInfo.isASCIIPrintable(next)) {
							if ((index + 1) == this.lineLength) {
								this.LOG.info("Line " + totalLines
										+ " contains more than "
										+ this.lineLength
										+ " printable bytes.\n");
							} else {
								if (index + 1 < this.lineLength) {
									resultStream.write(next);
									index++;
								}
							}

						}
					}
				} else {// just count line
					if (next == CharInfo.MACNEWLINE) {
						totalLines++;
						overflowLines++;
					}
				}
			}
			int actual;
			if (totalLines < this.numLines) {
				actual = totalLines;
			} else {
				actual = this.numLines;
			}
			this.LOG.info(actual + " lines were produced in file "
					+ this.resultOut.toString() + ".");
			this.LOG.info("File " + this.primIn + " contained " + totalLines
					+ " total.");
			this.LOG.info("There were " + overflowLines
					+ " lines that overflowed total.");

		} catch (FileNotFoundException e) {
			// TODO //Should already be handled? How do we treat this?
		} catch (IOException e) {
			// TODO //Should already be handled? How do we treat this?
		} finally {
			if (primStream != null) {
				try {
					primStream.close();
					resultStream.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
				}
			}
		}
	}
}
